#include "stdafx.h"


using namespace System;
using namespace System::Collections::Generic;
using namespace System::Windows;
using namespace System::Windows::Media;
using namespace System::Text;
using namespace System::Globalization;

#include "renderengine.h"
#include <vector>


#include "../fitz/include/fitz.h"
#include "../fitz/include/mupdf.h"


void deleteobject(pdf_xref* p)		{ pdf_closexref(p); }
void deleteobject(pdf_pagetree* p)	{ pdf_droppagetree(p); }
void deleteobject(pdf_page* p)		{ pdf_droppage(p); }
void deleteobject(fz_pixmap* p)		{ fz_droppixmap(p); }
void deleteobject(fz_renderer* p)	{ fz_droprenderer(p); }
void deleteobject(pdf_textline* p)	{ pdf_droptextline(p); }



template <class T>
class handle
{	
	handle<T>(handle<T>& h);
	handle<T>& operator=(const handle<T>&);

public:
	T* _p;

	explicit handle<T>(T* p = nullptr) 
	{ _p = p; }

	T* operator->() { return _p; }
	T** operator&() { return &_p; }

	
	handle<T>& operator=(T* p) { assign(p); return *this; }

	bool operator==(const T* p) { return _p == p; }
	bool operator==(const handle<T>& h) { return _p == h._p; }

	T* release()
	{
		T* p = _p;
		_p = nullptr;
		return p;
	}

	void assign(T* p)
	{
		if (_p != nullptr)
		{
 			deleteobject(_p);
		}
		_p = p;
	}

	~handle()
	{
		assign(nullptr);
	}

};
#pragma warning(disable:4100)




DWORD ByteSwap(DWORD uint)
{
	return (uint << 24) | 
		((uint << 8) & 0x00FF0000) |
		((uint >> 8) & 0x0000FF00) |
		(uint >> 24);

}


String^ FromAscii(char* pchar)
{
	StringBuilder^ builder = gcnew StringBuilder();
	
	for(int i = 0; pchar[i] != 0; ++i)
	{
		builder->Append(pchar[i]);
	}
	return builder->ToString();
}


static void cvt2wpfnode(DrawingContext ^context, fz_node *node);

static void cvt2wpfmeta(DrawingContext ^context, fz_metanode *node)
{
	fz_node *child;
	
	printf("(meta ");
	if (node->name) { printf(node->name); }
	if (node->dict) { printf("\n"); fz_debugobj((fz_obj*)node->dict); }
	printf("\n");
	for (child = node->super.first; child; child = child->next)
		cvt2wpfnode(context, child + 1);
	
	printf(")\n");
}

static void cvt2wpfover(DrawingContext ^context, fz_overnode *node)
{
	fz_node *child;
	
	printf("(over\n");
	for (child = node->super.first; child; child = child->next)
		cvt2wpfnode(context, child + 1);
	
	printf(")\n");
}

static void cvt2wpfmask(DrawingContext ^context, fz_masknode *node)
{
	fz_node *child;
	
	printf("(mask\n");
	for (child = node->super.first; child; child = child->next)
		cvt2wpfnode(context, child + 1);
	
	printf(")\n");
}

static void cvt2wpfblend(DrawingContext ^context, fz_blendnode *node)
{
	fz_node *child;
	
	printf("(blend-%d\n", node->mode);
	for (child = node->super.first; child; child = child->next)
		cvt2wpfnode(context, child + 1);
	
	printf(")\n");
}

static void cvt2wpftransform(DrawingContext ^context, fz_transformnode *node)
{

	context->PushTransform(gcnew MatrixTransform(node->m.a, node->m.b, node->m.c, node->m.d, node->m.e, node->m.f));

	cvt2wpfnode(context, node->super.first + 1);
	context->Pop();
}

static void cvt2wpfsolid(DrawingContext ^context, fz_solidnode *node)
{
	int i;
	
	printf("(color %s ", node->cs->name);
	for (i = 0; i < node->n; i++)
		printf("%g ", node->samples[i]);
	printf(")\n");
}

static void cvt2wpflink(DrawingContext ^context, fz_linknode *node)
{
	
	printf("(link %p)\n", node->tree);
}

static void cvt2wpfpath(DrawingContext ^context, fz_pathnode *node)
{
	int i;

	

	if (node->paint == FZ_STROKE)
	{
		printf("(path 'stroke %d %d %g %g ",
			node->linecap,
			node->linejoin,
			node->linewidth,
			node->miterlimit);
		if (node->dash)
		{
			printf("%g '( ", node->dash->phase);
			for (i = 0; i < node->dash->len; i++)
				printf("%g ", node->dash->array[i]);
			printf(")");
		}
		else
			printf("0 '()");
	}
	else
	{
		printf("(path '%s", node->paint == FZ_FILL ? "fill" : "eofill");
	}

	printf("\n");
	fz_debugpathnode(node);

	
	printf(")\n");
}



static void cvt2wpftext(DrawingContext ^context, fz_textnode *text)
{

	//fz_error *error;
	//fz_irect clip;
	//fz_matrix tm, trm;
	//fz_glyph glyph;
	//int i, x, y, cid;

//	tbox = fz_roundrect(fz_boundnode((fz_node*)text, ctm));
	//clip = fz_intersectirects(gc->clip, tbox);

	//if (fz_isemptyrect(clip))
	//	return nil;

	//if (!(gc->flag & FOVER))
	//{
	//	error = fz_newpixmapwithrect(&gc->dest, clip, 1);
	//	if (error)
	//		return error;
	//	fz_clearpixmap(gc->dest);
	//}

	
//	tm = text->trm;
	Typeface^ typeface = gcnew Typeface(FromAscii(text->font->name));
		
	for (int i = 0; i < text->len; i++)
	{
		int cid = text->els[i].cid;
		FormattedText^ formattedtext = gcnew FormattedText(System::Char::ConvertFromUtf32(cid), CultureInfo::CurrentUICulture, FlowDirection::LeftToRight, typeface, 15, Brushes::Black);
		context->DrawText(formattedtext, Point(text->els[i].x, text->els[i].y));

		//tm.e = text->els[i].x;
		//tm.f = text->els[i].y;
		//trm = fz_concat(tm, ctm);
		//trm.e = QUANT(trm.e - fz_floor(trm.e), HSUBPIX);
		//trm.f = QUANT(trm.f - fz_floor(trm.f), VSUBPIX);

		//error = fz_renderglyph(gc->cache, &glyph, text->font, cid, trm);
		//if (error)
		//	return error;

		//if (!(gc->flag & FOVER))
		//	drawglyph(gc, gc->dest, &glyph, x, y);
		//else
		//	drawglyph(gc, gc->over, &glyph, x, y);
	}


}

static void cvt2wpfimage(DrawingContext ^context, fz_imagenode *node)
{
	fz_image *image = node->image;
	
	
	printf("(image %dx%d %d+%d)\n", image->w, image->h, image->n, image->a);
}

static void cvt2wpfshade(DrawingContext ^context, fz_shadenode *node)
{
	
	printf("(shade)\n");
}

static void cvt2wpfnode(DrawingContext ^context, fz_node *node)
{
	if (!node)
	{
		
		printf("(nil)\n");
		return;
	}

	switch (node->kind)
	{
	case FZ_NMETA: cvt2wpfmeta(context,(fz_metanode*)node); break;
	case FZ_NOVER: cvt2wpfover(context,(fz_overnode*)node); break;
	case FZ_NMASK: cvt2wpfmask(context,(fz_masknode*)node); break;
	case FZ_NBLEND: cvt2wpfblend(context,(fz_blendnode*)node); break;
	case FZ_NTRANSFORM: cvt2wpftransform(context,(fz_transformnode*)node); break;
	case FZ_NCOLOR: cvt2wpfsolid(context,(fz_solidnode*)node); break;
	case FZ_NPATH: cvt2wpfpath(context,(fz_pathnode*)node); break;
	case FZ_NTEXT: cvt2wpftext(context,(fz_textnode*)node); break;
	case FZ_NIMAGE: cvt2wpfimage(context,(fz_imagenode*)node); break;
	case FZ_NSHADE: cvt2wpfshade(context,(fz_shadenode*)node); break;
	case FZ_NLINK: cvt2wpflink(context,(fz_linknode*)node); break;
	}
}



class LogTiming
{
public:
	LogTiming(char* pname)	
	{
		this->pname = pname;
		::QueryPerformanceCounter(&start);
	}

	~LogTiming()
	{		
		LARGE_INTEGER end;
		LARGE_INTEGER freq;
		::QueryPerformanceFrequency(&freq);
		::QueryPerformanceCounter(&end);

		char strFormat[160] = {0}; 
		sprintf(strFormat, "Time %s :%f\r", pname, 1000 * (double(end.QuadPart) - double(start.QuadPart)) / double(freq.QuadPart));
		::OutputDebugStringA(strFormat);

	}

	char* pname;
	LARGE_INTEGER start;
};



namespace pdfviewnet
{

	class PdfDocumentImpl : public PdfDocument
	{

		handle<pdf_xref> _xref;
		std::vector<pdf_page*> _pages;
		handle<pdf_pagetree> _pageTree;
		
	public:
		~PdfDocumentImpl()
		{}

		
		bool Load(char *);
		void RenderPage(System::Windows::Media::DrawingContext ^context, int ipage);
		BitmapSource^ RenderPage(int ipage, double zoom);
	
		int PageCount()
			{ return _pages.size(); }

		cli::array<System::Windows::Rect>^ GetLinks(int ipage);
		System::String^ GetPageText(int pageNo, List<System::Windows::Rect>^ rects);

		double ComputeDefaultZoom(int pageNo, double width, double height);
		Rect getPageBox(UINT pageNo);
		
		int findPageNo(Link linkId)
		{
			fz_obj* dest = GetLink(linkId)->dest;
			
			UINT p = 0;
			int n = fz_tonum(dest);
			int g = fz_togen(dest);

			while (p < _pages.size()) 
			{
				if (n == fz_tonum(_pageTree->pref[p]) && g == fz_togen(_pageTree->pref[p]))
					return p;

				p++;
			}

			return -1;
		}

		System::String^ GetLinkUri(Link linkId)
		{
			pdf_link* pdf_link = GetLink(linkId);
		
			if (!fz_isstring(pdf_link->dest))
				return System::String::Empty;

			if (fz_tostrlen(pdf_link->dest) >= 512)
				throw gcnew System::ArgumentException();
				
			char buf[512];
			memcpy(buf, fz_tostrbuf(pdf_link->dest), fz_tostrlen(pdf_link->dest));								
			return Marshal::PtrToStringAnsi(System::IntPtr(buf), fz_tostrlen(pdf_link->dest));						
		}
	private:
		pdf_page* getPdfPage(UINT pageNo);
		pdf_link* GetLink(Link link);

		fz_pixmap* RenderPageImpl(int ipage, double zoom);
		bool LoadPageTree();
		fz_matrix ComputeViewMatrix(pdf_page *page, float zoom);	
	};

	PdfDocument* PdfDocument::Create()
	{
		return new PdfDocumentImpl();
	};

	bool PdfDocumentImpl::Load(char* fileNameAnsi)
	{
		handle<pdf_xref>  xref;
		pdf_newxref(&xref);

		if (xref == nullptr)
			return false;

		fz_error* loadError = pdf_loadxref(xref._p, fileNameAnsi);
		//error = pdf_repairxref(_xref, (TCHAR*)fileName);
		if (loadError != nullptr)
			return false;

		fz_error* decryptionError = pdf_decryptxref(xref._p);

		if (decryptionError != nullptr)
			return false;

		if (xref->crypt) 
		{
			fz_error* passwordError = pdf_setpassword(xref->crypt, "");
			if (passwordError != nullptr)
				return false;
		}

		_xref = xref.release();
		xref = nullptr;
		LoadPageTree();

		return true;
	}

	bool PdfDocumentImpl::LoadPageTree()
	{
		pdf_pagetree* pageTree = nullptr;
		fz_error* error = pdf_loadpagetree(&pageTree, _xref._p);

		if (error != nullptr)
			return false;

		_pageTree = pageTree;			
		_pages.resize(_pageTree->count);

		return true;
	}



	String^ PdfDocumentImpl::GetPageText(int pageNo, List<Windows::Rect>^ rects)
	{			
		System::Text::StringBuilder^ bufline = gcnew System::Text::StringBuilder();
		pdf_page* page = getPdfPage(pageNo);
		if (page == nullptr)
			return nullptr;

		fz_matrix ctm = ComputeViewMatrix(page, 1);

		handle<pdf_textline> linestart;
		fz_error* error = pdf_loadtextfromtree(&linestart, page->tree, ctm);

		if (error != nullptr)
			return nullptr;

		pdf_textline* line = linestart._p;

		while (line != nullptr)
		{					
			for (int i = 0; i < line->len; ++i)
			{
				bufline->Append((wchar_t)line->text[i].c);
				if (rects != nullptr)
				{
					fz_irect box = line->text[i].bbox;
					rects->Add(Windows::Rect(Point(box.x0, box.y0), Point(box.x1, box.y1)));
				}					
			}
			bufline->Append("\r\n");

			if (rects != nullptr)
			{
				for (int i = 0; i < 2; ++i)
				{
					rects->Add(Windows::Rect());
				}				
			}

			line = line->next;
		}

		return bufline->ToString();		
	}


	Rect PdfDocumentImpl::getPageBox(UINT pageNo)
	{
		fz_rect bbox;
		int rotate;
		void* error = pdf_getpageinfo(_xref._p, pdf_getpageobject(_pageTree._p, pageNo), &bbox, &rotate);
		error;

		return Rect(Point(bbox.x0, bbox.y0),Point(bbox.x1, bbox.y1));			
	}


	pdf_page* PdfDocumentImpl::getPdfPage(UINT pageNo)
	{
		if (pageNo < 0 || pageNo >= _pages.size())
			return nullptr;

		if (_pages[pageNo] == nullptr)
		{
			LogTiming timing(__FUNCTION__);

			pdf_page* page = nullptr;
			fz_error * error = pdf_loadpage(&page, _xref._p, pdf_getpageobject(_pageTree._p, pageNo));
			if (error != nullptr)
				return nullptr;

			_pages[pageNo] = page;
		}

		return _pages[pageNo];
	}

	double PdfDocumentImpl::ComputeDefaultZoom(int pageNo, double width, double height)
	{
		if (Double::IsNaN(width * height))
			return 1.2;

		Rect rect = getPageBox(pageNo);


		return Math::Min(height*1.0 / rect.Height, width *1.0 / rect.Width);

	}

	
	fz_matrix PdfDocumentImpl::ComputeViewMatrix(pdf_page *page, float zoom)
	{
		fz_matrix ctm;
		ctm = fz_identity();
		ctm = fz_concat(ctm, fz_translate(-page->mediabox.x0 - (6 * (zoom - 1)), -page->mediabox.y1 + (6* (zoom - 1))));
		//ctm = fz_concat(ctm, fz_translate(page->mediabox.x1, page->mediabox.y0));
		ctm = fz_concat(ctm, fz_scale(zoom, -zoom));

		return ctm;
	}

	pdf_link* PdfDocumentImpl::GetLink(Link linkId)
	{
		pdf_page* page = getPdfPage(linkId._pageSource);
		pdf_link* link = page->links;

		int ilink = linkId._linkIndex;

		while (ilink > 0 && link != nullptr)
		{				
			link = link->next;
			ilink--;
		}
		return link;
	}


	cli::array<System::Windows::Rect>^ PdfDocumentImpl::GetLinks(int ipage)
	{
		pdf_page* page = getPdfPage(ipage);

		List<System::Windows::Rect>^ linklist = gcnew List<System::Windows::Rect>();

		if (page == nullptr)
			return linklist->ToArray();

		pdf_link* link = page->links;

		while (link != nullptr)
		{
			System::Windows::Rect rect = System::Windows::Rect(link->rect.x0, -link->rect.y1, link->rect.x1 - link->rect.x0, link->rect.y1 - link->rect.y0);
			rect.Offset(page->mediabox.x0, page->mediabox.y1);
			linklist->Add(rect);
			link = link->next;
		}

		return linklist->ToArray();						
	}

	void PdfDocumentImpl::RenderPage(DrawingContext ^context, int ipage)
	{
		cvt2wpfnode(context, getPdfPage(ipage)->tree->root);
	}

	BitmapSource^ PdfDocumentImpl::RenderPage(int ipage, double zoom)
	{
		handle<fz_pixmap> image(RenderPageImpl(ipage, zoom));

		if (image == nullptr)
			return nullptr;

		PixelFormat format = image->n == 1 ? PixelFormats::Gray8 : PixelFormats::Bgr32;

		return (BitmapSource^)BitmapSource::Create(image->w, image->h, 72 * zoom, 72 * zoom, format, nullptr, IntPtr(image->samples), image->w * image->h * image->n, image->w * image->n)->GetAsFrozen();

	}

	fz_pixmap* PdfDocumentImpl::RenderPageImpl(int ipage, double zoom)
	{
		handle<fz_renderer> renderer;

		pdf_page* page = getPdfPage(ipage);

		if (page == nullptr)
			return nullptr;

		LogTiming timing(__FUNCTION__);

		fz_matrix ctm  = ComputeViewMatrix(page, (float)zoom);
		fz_rect bbox   = fz_transformaabb(ctm, page->mediabox);

		fz_newrenderer(&renderer, pdf_devicergb, 0, 512 * 1024);
		if (renderer == nullptr)
			return nullptr;

		fz_pixmap* image = nullptr;
		fz_error* error = fz_rendertree(&image, renderer._p, page->tree, ctm, fz_roundrect(bbox), 1);	
		if (error != nullptr)
			return nullptr;

		if (image->n == 4)
		{
			DWORD* ppixel = (DWORD*)image->samples;
			for (int i = 0; i < image->w * image->h; ++i)
			{
				ppixel[i] = ByteSwap(ppixel[i]);
			}
		}

		return image;
	}	


}

